/* CVS: CobaltVault SERVER:\\halo TREE:\abode_mainline
 *       _    ____   ___  ____  _____ 
 *      / \  | __ ) / _ \|  _ \| ____|      Advanced
 *     / _ \ |  _ \| | | | | | |  _|        Behavior
 *    / ___ \| |_) | |_| | |_| | |___       Oriented
 *   /_/   \_\____/ \___/|____/|_____|      Design
 *         www.cobaltsoftware.net           Environment
 *
 * PRODUCED FOR:      University of Bath / Boeing
 * PAYMENT:           On Delivery   
 * LICENSING MODEL:   Unrestricted distribution (Post Delivery)
 * COPYRIGHT:         Client retains copyright.
 *
 * This program and all the software components herein are
 * released as-is, without warranties regarding function,
 * correctness or any other aspect of the components.
 * Steven Gray, Cobalt Software, it's subcontractors and
 * successors may not be held liable for any damage caused 
 * to computers, business or other property through use of 
 * or misuse of this software.
 *
 * Upon redistribution of the program, all notices of
 * copyrights, both of the software provider and the 
 * client must be retained.
 */
package abode.control;


import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;

import model.IEditableElement;
import model.TimeUnit;
import model.posh.ActionElement;
import model.posh.ActionPattern;
import model.posh.Competence;
import model.posh.CompetenceElement;
import model.posh.DriveCollection;
import model.posh.DriveElement;
import model.posh.LearnableActionPattern;

/**
 * DotLapWriter is responsible for exporting our learnable action pattern object
 * as a .lap file.
 * 
 * @author CobaltSoftware (abode.devteam@cobaltsoftware.net)
 * @version 1.0
 */
public class DotLapWriter implements ILapWriter {
	/**
	 * Save data to a file
	 * 
	 * @param fileName
	 *            Path to file to save to.
	 * @param data
	 *            Object model of this file
	 */
	@Override
	public void save(String fileName, LearnableActionPattern data) {
		try {
			PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
			writer.println("; This file was generated by A.B.O.D.E.");
			writer.println("; Do not add comments to this file directly, as they may be");
			writer.println("; lost the next time the tool is used.");
			writer.println("; -------------------------------------------------------------");
			writer.print(generateLispFromLAP(data));
			writer.close();
		} catch (IOException e) {
			System.out.println("ERROR SAVING FILE: " + e.toString());
		}
	}

	/**
	 * Process the LearnableActionPattern object
	 * 
	 * @param data
	 *            The object model
	 * @return Lisp string representation
	 */
	@Override
	public String generateLispFromLAP(LearnableActionPattern data) {

		// Initial opening bracket
		String result = "(\n";

		// Parse documentation tags
		if (data.getDocumentation() != null) {
			result += "(documentation \"" + data.getDocumentation().getTitle() + "\" \"" + data.getDocumentation().getAuthor() + "\" \"" + data.getDocumentation().getMemo() + "\")\n";
		}

		// Iterate over all elements of the file
		Iterator iterator = data.getElements().iterator();
		while (iterator.hasNext()) {
			IEditableElement element = (IEditableElement) iterator.next();

			if (element instanceof ActionPattern){
				result += generateCommentsFromElement(element, 0, true);
				result += "" + generateLispFromActionPattern((ActionPattern) element) + "\n";
			}
		}

		iterator = data.getElements().iterator();
		while (iterator.hasNext()) {
			IEditableElement element = (IEditableElement) iterator.next();

			if (element instanceof Competence){
				result += generateCommentsFromElement(element, 0, true);
				result += generateLispFromCompetence((Competence) element) + "\n";
			}
		}

		iterator = data.getElements().iterator();
		while (iterator.hasNext()) {
			IEditableElement element = (IEditableElement) iterator.next();

			if (element instanceof DriveCollection){
				result += generateCommentsFromElement(element, 0, true);
				result += generateLispFromDriveCollection((DriveCollection) element) + "\n";
			}
		}
		// Add the final closing bracket
		result = result + ")";

		return result;
	}

	/**
	 * Convert a drive collection into it's lisp representation
	 */
	private String generateLispFromDriveCollection(DriveCollection driveCollection) {
		String result;

		result = "(" + (driveCollection.getStrictMode() ? "S" : "") + (driveCollection.getRealTime() ? "R" : "") + "DC " + driveCollection.getName() + " " + generateLispFromGoal(driveCollection.getGoal()) + "\n";
		result += "\t(drives\n";

		Iterator outer = driveCollection.getDriveElements().iterator();
		while (outer.hasNext()) {
			Iterator inner = ((ArrayList) outer.next()).iterator();
			while (inner.hasNext()) {
				DriveElement driveElement = (DriveElement) inner.next();
				if (driveElement.isEnabled()) {
					result += "\t\t(";
				} else {
					result += "\t\t;(";
				}
				result += generateLispFromDriveElement(driveElement);
			}
			result += ")\n";
		}
		result += "\n\t)\n)";

		return result;
	}

	/**
	 * Generate a block of lisp from a drive element
	 * 
	 * @param driveElement
	 *            A Drive Element
	 * @return Lisp representation of element
	 */
	private String generateLispFromDriveElement(DriveElement driveElement) {
		String result = "\n";
		// If there are comments, then add them!
		result += generateCommentsFromElement(driveElement, 2, false) + "\n";
		
		result += "\t\t(" + driveElement.getName() + " " + generateLispFromTrigger(driveElement.getTrigger()) + " " + driveElement.getAction();
		if (driveElement.getFrequency() != null)
			result += generateLispFromTimeUnit(driveElement.getFrequency());
		result += ")";
		return result;
	}

	/**
	 * Convert a competence into lisp form
	 * 
	 * @param competence
	 *            A Competence
	 * @return Lisp representation of the competence
	 */
	private String generateLispFromCompetence(Competence competence) {
		String result;
		// Add the header, name, timeout
		if (competence.isEnabled()) {
			result = "(C " + competence.getName() + " " + generateLispFromTimeUnit(competence.getTimeout()) + " " +  generateLispFromGoal(competence.getGoal()) + "\n\t";
		} else {
			result = ";(C " + competence.getName() + " " + generateLispFromTimeUnit(competence.getTimeout()) + " " + generateLispFromGoal(competence.getGoal()) + "\n\t;";
		}
		// Parse the goal
		result += "(elements\n";

		Iterator outer = competence.getElementLists().iterator();
		while (outer.hasNext()) {
			ArrayList outerList = (ArrayList) outer.next();
			Iterator inner = outerList.iterator();
			if (competence.isEnabled()) {
				result += "\t\t(";
			} else {
				result += "\t\t;(";
			}
			while (inner.hasNext()) {
				CompetenceElement element = (CompetenceElement)inner.next();
				result += "\t";
				System.out.println("Competence: " + element.getName() + ", Enabled: " + element.isEnabled());
				result += generateLispFromCompetenceElement(element);
			}
			result += "\t)\n";
		}

		//Close elements and C
		if (competence.isEnabled()) {
			result += "\t)\n)";
		} else {
			result += "\t;)\n;)";
		}

		return result;
	}

	/**
	 * Convert a competence element into it's lisp representation
	 * 
	 * @param compElement
	 *            Competence Element
	 * @return Lisp representation of the competence element
	 */
	private String generateLispFromCompetenceElement(CompetenceElement compElement) {
		String result = "(" + compElement.getName() + " " + generateLispFromTrigger(compElement.getTrigger()) + " " + compElement.getAction();
		if (compElement.getRetries() > 0)
			result += " " + compElement.getRetries();
		result += ")";
		return result;
	}

	/**
	 * A trigger is stored as an arraylist of actionelements
	 * 
	 * @param trigger
	 *            Arraylist of action elements comprising the trigger
	 * @return String/lisp representation of the trigger
	 */
	private String generateLispFromTrigger(ArrayList trigger) {
		String result = "(trigger (";
		Iterator iterator = trigger.iterator();
		while (iterator.hasNext()) {
			ActionElement actionElement = (ActionElement) iterator.next();
			result = result + generateLispFromActionElement(actionElement);
			if (iterator.hasNext())
				result += " ";
		}
		result += "))";
		return result;
	}

	/**
	 * A Goal is stored as an arraylist of actionelements, so parse that into a
	 * lispy-format
	 * 
	 * @param goal
	 *            Arraylist represetnation of goal, which is really just the
	 *            same as an arraylist of action elements
	 * @return Lisp represntation of the goal
	 */
	private String generateLispFromGoal(ArrayList goal) {
		String result = "(goal (";
		Iterator iterator = goal.iterator();
		while (iterator.hasNext()) {
			ActionElement actionElement = (ActionElement) iterator.next();
			result += generateLispFromActionElement(actionElement);
			if (iterator.hasNext())
				result += " ";
		}
		result += "))";
		return result;
	}

	/**
	 * Convert an ActionPattern object into lisp
	 * 
	 * @param pattern
	 *            Action pattern construct
	 * @return Lisp representation of the action pattern
	 */
	private String generateLispFromActionPattern(ActionPattern pattern) {
		String result;
		result = "(AP " + pattern.getName() + " " + generateLispFromTimeUnit(pattern.getTimeUnit()) + " (";
		Iterator iterator = pattern.getElements().iterator();
		while (iterator.hasNext()) {
			ActionElement element = (ActionElement) iterator.next();
			if (element.isEnabled()) {
				result += "\n\t";
			} else {
				result += "\n\t;";
			}
			
			result += generateLispFromActionElement(element) + (iterator.hasNext() ? " " : "\n");
		}

		// Close the list of actions and the AP itself
		result += "))";
		return result;
	}

	/**
	 * Convert an action element into lisp
	 * 
	 * @param element
	 *            Action Element object
	 * @return Lisp representation of an action pattern element
	 */
	private String generateLispFromActionElement(ActionElement element) {
		String result = "";
		
		if (!element.getIsSense()) {
			result += element.getElementName();
			return result;
		}

		//Otherwise this is a sense
		result = "(" + element.getElementName();

		// If we have a value, add it and a leading space
		if (element.getValue() != null) {
			result += " " + element.getValue();

			// If we also have a predicate, add it and another leading space
			if (element.getPredicate() != null) {
				result += " " + element.getPredicate();
			}
		}

		// Add final ) to close the list
		result += ")";

		return result;
	}

	/**
	 * Convert a timeunit into a lisp element
	 * 
	 * @param timeUnit
	 *            A measure of time
	 * @return String/lisp representation
	 */
	private String generateLispFromTimeUnit(TimeUnit timeUnit) {
		return "(" + timeUnit.getUnitName() + " " + timeUnit.getUnitValue() + ")";
	}
	
	/**
	 * Returns a comment string for a given element.
	 * 
	 * @param element
	 * 			An IEditableElement
	 * 		  tabCount
	 * 			the number of tabs the comment should be idented by
	 * 	      newLine
	 * 		    whether the comment should be suffixed by a new line
	 * @return String for the comment including starting character ";".
	 */
	private String generateCommentsFromElement(IEditableElement element, int tabCount, boolean newLine){
		String strDocumentation = element.getElementDocumentation();
		String result = "";
		
		// Do we have a valid string?
		if(strDocumentation != null &&
				strDocumentation.length() > 1){
			
			// Determine what new lines should be replaced with
			String newLineReplaceString = "\n";
			for(int i = 0; i < tabCount; i++){
				newLineReplaceString += "\t";
			}
			newLineReplaceString += "; ";
			
			// Start by tabbing
			for(int i = 0; i < tabCount; i++){
				result += "\t";
			}
			
			// Get the final result
			if(newLine){
				result += "; " + strDocumentation.replaceAll("\n", newLineReplaceString) + "\n";
			}
			else{
				result += "; " + strDocumentation.replaceAll("\n", newLineReplaceString);
			}
		}
		
		return result;
	}
}
